package gouhuo.allbugs.top.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import gouhuo.allbugs.top.common.BusinessException;
import gouhuo.allbugs.top.common.ErrorCode;
import gouhuo.allbugs.top.constant.RedisConstants;
import gouhuo.allbugs.top.constant.UserConstant;
import gouhuo.allbugs.top.mapper.UserMapper;
import gouhuo.allbugs.top.model.domain.User;
import gouhuo.allbugs.top.model.vo.ChatVO;
import gouhuo.allbugs.top.model.vo.UserVO;
import gouhuo.allbugs.top.service.UserService;
import gouhuo.allbugs.top.utils.AlgorithmUtil;
import gouhuo.allbugs.top.utils.DESUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static gouhuo.allbugs.top.constant.RedisConstants.CHAT_MSG_KET;

/**
 * @author 15294780204
 * @description 针对表【user(用户表)】的数据库操作Service实现
 * @createDate 2022-11-03 19:47:32
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedisTemplate redisTemplate;

    //随机生成编号的基数
    int counter = 1;
    //随机生成昵称的基数
    int name = 1;
    //系统消息后缀
    long systemCount = 0;

    @Override
    public List<UserVO> searchUsers(String searchMessage) {
        List<User> userList = list(new QueryWrapper<User>().like("username", searchMessage));
        return userList.stream().map(user -> BeanUtil.copyProperties(user, UserVO.class)).collect(Collectors.toList());
    }

    /**
     * 根据完整信息注册
     *
     * @param phone
     * @param userPassword  用户密码
     * @param checkPassword
     * @param userName
     * @param email
     * @param gender
     * @return
     */
    @Override
    public long userRegister(String phone, String userPassword, String checkPassword, String userName, String email, Integer gender) {
        // 校验
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        // 密码和校验密码是否相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次密码不一致");
        }
        // 账户或手机号不能重复
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        long count = count(userQueryWrapper.eq("phone", phone));
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "您的手机号已经注册过了");
        }
        //  加密
//        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 插入数据
        User user = new User();
        user.setPhone(phone);
        user.setUserPassword(userPassword);
        user.setUsername(userName);
        user.setEmail(email);
        user.setUserStatus(0);
        user.setUserRole(0);
        user.setGender(gender);
        user.setAvatarUrl("http://cdn.allbugs.top/kun.png");
        user.setPlanetCode(randomPlantCode());
        boolean saveResult = save(user);
        if (!saveResult) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "注册失败");
        }
        Long userId = user.getId();
        allUserMsg(userId);
        redisUserListUpdate();
        return userId;
    }

    public void allUserMsg(Long userId) {
        String hashKey = "system" + systemCount;
        String k = CHAT_MSG_KET + userId + ":msg";
        ChatVO chatVO = new ChatVO();
        chatVO.setToId(userId);
        chatVO.setFromName("小智-欢迎新人");
        chatVO.setFromUrl("http://cdn.allbugs.top/kun.png");
        chatVO.setText("欢迎来到篝火V2.0，可直接加\"纯鹿人\"为好友而无需等待同意");
        chatVO.setScope(3);
        chatVO.setCreateTime(String.valueOf(System.currentTimeMillis()));
        chatVO.setSysStatus(hashKey);
        stringRedisTemplate.opsForHash().put(k, hashKey, new Gson().toJson(chatVO));
        String statusKey = CHAT_MSG_KET + userId + ":status";
        stringRedisTemplate.opsForSet().add(statusKey, hashKey);
    }

    /**
     * 随机生成6位数字做编号
     *
     * @return
     */
    public String randomPlantCode() {
        Random random = new Random();
        int num = random.nextInt(900000) + 100000;
        return String.valueOf(num + counter++);
    }

    /**
     * 每次有人注册都刷新redis的用户列表
     */
    public void redisUserListUpdate() {
        List<User> list = list();
        if (list == null) {
            return;
        }
        redisTemplate.opsForValue().set(RedisConstants.USER_LIST_KEY, list, RedisConstants.USER_LIST_TTL, TimeUnit.MINUTES);
    }

    /**
     * 根据手机号注册
     *
     * @param phone
     * @return
     */
    public User userRegister(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setUserPassword("gouhuo123456");
        user.setPlanetCode(randomPlantCode());
        user.setGender(1);
        user.setUserStatus(0);
        user.setUserRole(0);
        user.setAvatarUrl("http://cdn.allbugs.top/kun.png");
        user.setUsername("篝火_" + (name++));
        save(user);
        allUserMsg(user.getId());
        redisUserListUpdate();
        return user;
    }

    @Override
    public UserVO userLogin(String phone, String userPassword, String userCode, int remember, int userId, HttpServletRequest request, HttpServletResponse response) {
        User user;
        if (StrUtil.isNotBlank(userPassword)) {
            if (userPassword.length() < 8) {
                return null;
            }
            // 查询用户是否存在
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("phone", phone);
            user = getOne(queryWrapper);
            if (user == null) {
                return new UserVO();
            }
            //查询密码是否正确
            queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("phone", phone);
            queryWrapper.eq("userPassword", userPassword);
            user = getOne(queryWrapper);
            if (user == null) return null;
        } else if (StrUtil.isNotBlank(userCode)) {
            String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
            if (cacheCode == null || !cacheCode.equals(userCode)) {
                return null;
            }
            user = query().eq("phone", phone).one();
            if (user == null) {
                user = userRegister(phone);
            }
        } else {
            user = getById(userId);
        }
        UserVO userVO = new UserVO();
        //将当前登录用户的信息写入redis
        String key = RedisConstants.LOGIN_USER_KEY + request.getSession().getId();
        BeanUtil.copyProperties(user, userVO);
        //将user对象转化为HashMap存储
        Map<String, Object> userMap = BeanUtil.beanToMap(userVO, new HashMap<>(),
                CopyOptions.create() //在存储为map时要保证值全为String
                        .setIgnoreNullValue(true) //忽略空值
                        .setFieldValueEditor((fieldName, fieldValue) -> fieldValue == null ? "" : fieldValue.toString()));
        stringRedisTemplate.opsForHash().putAll(key, userMap);
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 默认实现七天免登录
        if (remember == 1) {
            // 将 login_ + userVO.getId() 加密并生成cookie返回客户端
            String encryption = DESUtil.encryption("login_" + userVO.getId());
            Cookie cookie = new Cookie("loginCookie", encryption);
            //设置cookie失效时间(秒)-7天和路径
            cookie.setMaxAge(604800);
            cookie.setPath("/api");
            response.addCookie(cookie);
        }
        //将用户标记为在线
        stringRedisTemplate.opsForHash().put(RedisConstants.LOGIN_ONLINE_KEY, String.valueOf(userVO.getId()), String.valueOf(System.currentTimeMillis() / 1000));
        return userVO;
    }

    /**
     * 用户注销
     *
     * @param request
     * @return
     */
    @Override
    public Boolean userLogout(Long userId, HttpServletRequest request, HttpServletResponse response, String cookie) {
        //清除在线状态
        stringRedisTemplate.opsForHash().delete(RedisConstants.LOGIN_ONLINE_KEY, String.valueOf(userId));
        //清除登陆用户信息
        String key = RedisConstants.LOGIN_USER_KEY + request.getSession().getId();
        if (StrUtil.isNotBlank(cookie)) {
            //清除cookie
            Cookie newCookie = new Cookie("loginCookie", null);
            newCookie.setMaxAge(0);
            newCookie.setPath("/api");
            response.addCookie(newCookie);
        }
        return stringRedisTemplate.delete(key);
    }

    @Override
    public boolean fromUserSpeak(UserVO loginUser, int star, String speak) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(User::getUserSpeak).eq(User::getId, loginUser.getId());
        User currUser = list(queryWrapper).get(0);
        String userSpeak;
        if (currUser != null) {
            userSpeak = currUser.getUserSpeak() + ",{" + star + ":" + speak + "}";
        } else {
            userSpeak = "{" + star + ":" + speak + "}";
        }
        User user = getById(loginUser.getId());
        user.setUserSpeak(userSpeak);
        return updateById(user);
    }

    /**
     * 根据标签搜索用户（内存过滤）
     *
     * @param tagNameList 用户要拥有的标签
     * @return
     */
    @Override
    public List<UserVO> searchUsersByTags(List<String> tagNameList) {
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 1. 先查询所有用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> userList = list(queryWrapper);
        // 2. 在内存中判断是否包含要求的标签
        return userList.stream().filter(user -> {
            String tagsStr = user.getTags();
            Set<String> tempTagNameSet = new Gson().fromJson(tagsStr, new TypeToken<Set<String>>() {
            }.getType());
            tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());
            for (String tagName : tagNameList) {
                if (!tempTagNameSet.contains(tagName)) {
                    return false;
                }
            }
            return true;
        }).map(user -> BeanUtil.copyProperties(user, UserVO.class)).collect(Collectors.toList());
    }

    /**
     * 按字段更新redis中的信息
     *
     * @param key
     * @param field
     * @param value
     */
    public void updateRedisByUser(String key, String field, String value, Long userId, String dyKey) {
        Set<String> keys = stringRedisTemplate.keys(key);
        if (keys == null || keys.isEmpty()) {
            return;
        }
        for (String k : keys) {
            String[] split = k.split(":");
            if (split[split.length - 1].equals(dyKey + userId)) {
                stringRedisTemplate.opsForHash().put(k, field, value);
            }
        }
    }

    @Override
    public boolean updateUser(User user, UserVO loginUser, HttpServletRequest request) {
        long userId = user.getId();
        if (userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        if (!isAdmin(loginUser) && userId != loginUser.getId()) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        String field = "", value = "";
        String username = user.getUsername();
        if (StrUtil.isNotBlank(username)) {
            field = "username";
            value = username;
        }
        Integer gender = user.getGender();
        if (gender != null) {
            field = "gender";
            value = String.valueOf(gender);
        }
        String email = user.getEmail();
        if (StrUtil.isNotBlank(email)) {
            field = "email";
            value = email;
        }
        String tags = user.getTags();
        if (StrUtil.isNotBlank(tags)) {
            field = "tags";
            String json = new Gson().toJson(tags.split("-"));
            user.setTags(json);
            value = json;
        }
        String passwordDouble = user.getUserPassword();
        if (StrUtil.isNotBlank(passwordDouble)) {
            String[] password = passwordDouble.split("-");
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", userId);
            queryWrapper.eq("userPassword", password[1]);
            User one = getOne(queryWrapper);
            if (one == null) return false;
            user.setUserPassword(password[0]);
        }
        boolean update = updateById(user);
        //更新需要展示当前用户信息的redis数据:登录用户和好友列表
        stringRedisTemplate.opsForHash().put(RedisConstants.LOGIN_USER_KEY + request.getSession().getId(), field, value);
        updateRedisByUser("visitor:full:*", field, value, userId, "visitor");
        return update;
    }

    @Override
    public UserVO getLoginUser(HttpServletRequest request, Boolean loop) {
        String key = RedisConstants.LOGIN_USER_KEY + request.getSession().getId();
        //将查询到的Hash数据转化为UserVO对象并直接返回, isIgnoreError:false 不要忽略转换中的错误
        Map<Object, Object> redisUserMap = stringRedisTemplate.opsForHash().entries(key);
        UserVO userVO = null;
        if (!redisUserMap.isEmpty()) {
            userVO = BeanUtil.fillBeanWithMap(redisUserMap, new UserVO(), false);
        }
        if (userVO == null && loop != null) {
            return new UserVO();
        }
        stringRedisTemplate.opsForHash().put(RedisConstants.LOGIN_ONLINE_KEY, String.valueOf(userVO.getId()), String.valueOf(System.currentTimeMillis() / 1000));
        return userVO;
    }

    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        UserVO userVO = getLoginUser(request, null);
        return userVO != null && userVO.getUserRole() == UserConstant.ADMIN_ROLE;
    }

    /**
     * 是否为管理员
     *
     * @param userVO
     * @return
     */
    @Override
    public boolean isAdmin(UserVO userVO) {
        return userVO != null && userVO.getUserRole() == UserConstant.ADMIN_ROLE;
    }

    @Override
    public List<UserVO> matchUsers(long num, UserVO loginUser) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("id", "tags").isNotNull("tags");
        List<User> userList = list(queryWrapper);
        String tags = loginUser.getTags();
        Gson gson = new Gson();
        List<String> tagList = gson.fromJson(tags, new TypeToken<List<String>>() {
        }.getType());
        // 用户列表<user对象,相似度>
        List<Pair<User, Long>> list = new ArrayList<>();
        // 依次计算所有用户和当前用户的相似度
        for (User user : userList) {
            String userTags = user.getTags();
            // 无标签或者为当前用户自己
            if (user.getId().equals(loginUser.getId())) {
                continue;
            }
            List<String> userTagList = gson.fromJson(userTags, new TypeToken<List<String>>() {
            }.getType());
            // 计算分数
            long distance = AlgorithmUtil.minDistance(tagList, userTagList);
            list.add(new Pair<>(user, distance));
        }
        if (list.isEmpty()) {
            return new ArrayList<>();
        }
        // 按编辑距离由小到大排序
        List<Pair<User, Long>> topUserPairList = list.stream()
                .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                .collect(Collectors.toList());

        // 排序后的 userId 列表
        List<Long> userIdList = topUserPairList.stream().map(pair -> pair.getKey().getId()).collect(Collectors.toList());
        // 转换为 UserVO 并以id分组(乱序)
        Map<Long, List<UserVO>> userIdUserListMap = list(new QueryWrapper<User>().in("id", userIdList))
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserVO.class))
                .collect(Collectors.groupingBy(UserVO::getId));
        List<UserVO> finalUserList = new ArrayList<>();
        //根据之前的 userId 列表的顺序收集 userVO
        for (Long userId : userIdList) {
            finalUserList.add(userIdUserListMap.get(userId).get(0));
        }
        return finalUserList;
    }

    /**
     * 根据标签搜索用户（SQL 查询版）
     *
     * @param tagNameList 用户要拥有的标签
     * @return
     */
    @Deprecated
    private List<UserVO> searchUsersByTagsBySQL(List<String> tagNameList) {
        if (CollectionUtils.isEmpty(tagNameList)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // 拼接 and 查询
        // like '%Java%' and like '%Python%'
        for (String tagName : tagNameList) {
            queryWrapper = queryWrapper.like("tags", tagName);
        }
        List<User> userList = list(queryWrapper);
        return userList.stream().map(user -> BeanUtil.copyProperties(user, UserVO.class)).collect(Collectors.toList());
    }

}